/**
 * element元素相关js方法
 */

const QilinNumber=require("./number.js");
const QilinString=require("./string.js");

module.exports={
    /**
     * 判断传入的参数是否是元素节点
     * @param {Element} element 传入的元素节点
     * @returns 返回布尔值
     */
    isElement(element){
        if(element && element.nodeType === 1 && element instanceof Node){
            return true;
        }else{
            return false;
        };
    },
    /**
     * 判断某个节点是否包含指定节点--可以是父子祖孙关系亦可是相等关系
     * @param {Element} parentNode 父节点
     * @param {Element} childNode  子节点
     * @returns 返回布尔值
     */
    isContains(parentNode,childNode){
        if(!this.isElement(parentNode)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!this.isElement(childNode)){
            throw new TypeError("第二个参数必须是一个元素");
        }else if(parentNode === childNode){
            return true;
        }else if(parentNode.contains){ //如果浏览器支持contains
            return parentNode.contains(childNode);
        }else if(parentNode.compareDocumentPosition){ //火狐支持
            return !!(parentNode.compareDocumentPosition(childNode) & 16);
        };
    },
    /**
     * 判断某个节点是否是指定节点的父节点
     * @param {Element} parentNode 父节点
     * @param {Element} childNode 子节点
     * @returns 返回布尔值
     */
    isParentNode(parentNode,childNode){
        if(!this.isElement(parentNode)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!this.isElement(childNode)){
            throw new TypeError("第二个参数必须是一个元素");
        }else if(parentNode === childNode){
            return false;
        }else{
            return childNode.parentNode === parentNode;
        };
    },
    /**
     * 移除某个元素的指定类名
     * @param {Element} element 元素节点
     * @param {String} className 类名--一个或多个（以英文逗号隔开）
     */
     removeClass(element,className){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!className || typeof className !== "string"){
            throw new TypeError("第二个参数必传且是一个字符串");
        }else{
            let classList=element.classList;
            let classArray=QilinString.trimString(className).split(","); //以英文逗号分割
            classArray.forEach((item)=>{
                classList.remove(item);
            });
        };
    },
    /**
     * 给某个元素添加指定类名
     * @param {Element} element 元素节点
     * @param {String} className 类名--一个或多个（以英文逗号隔开）
     */
    addClass(element,className){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!className || typeof className !== "string"){
            throw new TypeError("第二个参数必传且是一个字符串");
        }else{
            let classList=element.classList;
            let classArray=QilinString.trimString(className).split(","); //以英文逗号分割
            classArray.forEach((item)=>{
                classList.add(item);
            });
        };
    },
    /**
     * 判断某个元素是否包含指定类名
     * @param {Element} element 元素节点
     * @param {String} className 类名--一个或多个（以英文逗号隔开）
     * @returns 返回布尔值
     */
    hasClass(element,className){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!className || typeof className !== "string"){
            throw new TypeError("第二个参数必传且是一个字符串");
        }else{
            let classList=element.classList;
            let classArray=QilinString.trimString(className).split(","); //以英文逗号分割
            return classArray.every((item)=>{
                return classList.contains(item);
            });
        };
    },
    /**
     * 判断选择器字符串是哪种类型的选择器
     * @param {String} selector 选择器字符串
     * @returns 返回对象--包含选择器类型和选择器值
     */
    getCssSelector(selector){
        if(!selector || typeof selector !== "string"){
            throw new TypeError("参数必传且须为字符串");
        }else if(/^#{1}/.test(selector)){ //id选择器
            return {
                type:"id",
                value:selector.slice(1)
            };
        }else if(/^\./.test(selector)){ //类选择器
            return {
                type:"class",
                value:selector.slice(1)
            };
        }else if(/^\[(.+)\]$/.test(selector)){ //属性选择器
            let type="attribute";
            let value="";
            let attribute=QilinString.trimString(selector,true).substring(1,QilinString.trimString(selector,true).length - 1);
            let array=attribute.split("=");
            if(array.length === 1){
                value=array[0];
            }else if(array.length === 2){
                value={
                    attributeName:array[0],
                    attributeValue:array[1].replace(/\'/g,"").replace(/\"/g,"")
                };
            };
            return {
                type,
                value
            };
        }else{
            return {
                type:"tag",
                value:selector
            };
        };
    },
    /**
     * 获取某个元素距离指定祖先元素上下左右的距离--包含滚动条的距离
     * @param {Element} element 指定元素--需要设置相对定位或绝对定位
     * @param {Element} parentNode 父元素或祖先元素--需要设置相对定位或绝对定位，未指定就默认为document.body
     * @returns 返回对象-包含各个方向的距离
     */
    getNodeDistance(element,parentNode){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!this.isElement(parentNode)){
            parentNode=document.body;
        }else if(!this.isContains(parentNode,element)){
            throw new Error("第二个参数与第一个参数须有等级层次关系");
        };
        let obj=element;
        let offsetTop=0,offsetLeft=0;
        while(this.isElement(element) && this.isContains(parentNode,element) && parentNode !== element){
            offsetTop+=element.offsetTop;
            offsetLeft+=element.offsetLeft;
            element=element.offsetParent;
        };
        let offsetRight=parentNode.offsetWidth-offsetLeft-obj.offsetWidth;
        let offsetBottom=parentNode.offsetHeight-offsetTop-obj.offsetHeight;
        return {
            top:offsetTop,
            left:offsetLeft,
            right:offsetRight,
            bottom:offsetBottom
        };
    },
    /**
     * 获取某个元素节点下指定选择器的子元素节点
     * @param {Element} element 元素节点
     * @param {Element} selector 支持多选择器，等同于querySelectorAll的参数-不传默认查全部
     * @returns 返回包含子元素节点的数组
     */
    getChildNode(element,selector){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(selector && typeof selector !== "string"){
            throw new TypeError("第二个参数必须是一个字符串");
        }else{
            const result=element.querySelectorAll(selector || "*");
            return [...result].filter((item)=>{
                return item.parentNode === element;
            });
        };
    },
    /**
     * 获取某个元素节点指定选择器的兄弟元素节点
     * @param {Element} element 元素节点
     * @param {Element} selector 支持多选择器，等同于querySelectorAll的参数-不传默认查全部
     * @returns 返回包含兄弟元素节点的数组
     */
    getSiblingNode(element,selector){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(selector && typeof selector !== "string"){
            throw new TypeError("第二个参数必须是一个字符串");
        }else if(!element.parentNode){
            return [];
        }else{
            const result=element.parentNode.querySelectorAll(selector || "*");
            return [...result].filter((item)=>{
                return item.parentNode === element.parentNode && item !== element;
            });
        };
    },
    /**
     * 获取某个元素的指定样式
     * @param {Element} element 元素节点 
     * @param {String} cssName 样式名
     * @returns 返回样式值
     */
    getNodeCssStyle(element,cssName){
        if(!this.isElement(element)){
            throw new TypeError("第一个参数必须是一个元素");
        }else if(!cssName || typeof cssName !== "string"){
            throw new TypeError("第二个参数必传且必须是一个字符串");
        }else if(document.defaultView && document.defaultView.getComputedStyle){ //不兼容IE9以下浏览器，兼容其他
            return document.defaultView.getComputedStyle(element)[cssName];
        }else{ //兼容IE7-IE11，不兼容chrome、firefox、safari、opera
            return element.currentStyle[cssName];
        };
    },
    /**
     * 将rem单位转换为px单位
     * @param {Number} rem rem数值 
     * @returns 返回单位数值
     */
    remToPx(rem){
        if(!QilinNumber.isNumber(rem)){
            throw new TypeError("参数必须是一个数字");
        }else{
            const fontSize=this.getNodeCssStyle(document.documentElement,"font-size");
            return QilinNumber.multiplyNumber(rem,parseFloat(fontSize));
        };
    },
    /**
     * 将px单位转换位rem单位
     * @param {Number} px px数值
     * @returns 返回单位数值
     */
    pxToRem(px){
        if(!QilinNumber.isNumber(px)){
            throw new TypeError("参数必须是一个数字");
        }else{
            const fontSize=this.getNodeCssStyle(document.documentElement,"font-size");
            return QilinNumber.divideNumber(px,parseFloat(fontSize));
        };
    },
    /**
     * 将字符串类型的DOM转换为DOM元素节点
     * @param {String} HTMLString 字符串类型的dom节点
     * @returns 返回DOM元素节点或者包含DOM元素节点的DOM节点数组
     */
    stringToDom(HTMLString){
        if(!HTMLString || typeof HTMLString !== "string"){
            throw new TypeError("参数必传且须为字符串类型元素节点");
        }else{
            let parentNode=document.createElement("div");
            parentNode.innerHTML=HTMLString;
            if(parentNode.children.length === 1){
                return parentNode.children.item(0);
            }else{
                return parentNode.children;
            };
        };
    },
    /**
     * 获取某个元素的内容宽度
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是body元素
     * @returns 返回内容宽度值
     */
    getNodeContentWidth(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        }else if(!this.isElement(element)){
            element=document.body;
        };
        let clientWidth=element.clientWidth; //获取元素包括内边距在内的宽度
        let paddingLeft=parseFloat(this.getNodeCssStyle(element,"padding-left")); //左内边距
        let paddingRight=parseFloat(this.getNodeCssStyle(element,"padding-right")); //右内边距
        return QilinNumber.minusNumber(clientWidth,paddingLeft,paddingRight);
    },
    /**
     * 获取某个元素的内容高度
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是body元素
     * @returns 返回内容高度值
     */
    getNodeContentHeight(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        }else if(!this.isElement(element)){
            element=document.body;
        };
        let clientHeight=element.clientHeight; //获取元素包括内边距在内的高度
        let paddingTop=parseFloat(this.getNodeCssStyle(element,"padding-top")); //上内边距
        let paddingBottom=parseFloat(this.getNodeCssStyle(element,"padding-bottom")); //下内边距
        return QilinNumber.minusNumber(clientHeight,paddingTop,paddingBottom);
    },
    /**
     * 获取某个元素的总宽度
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认表示整个页面文档
     * @returns 返回元素总宽度值
     */
    getNodeWidth(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        };
        let scrollWidth=0;
        if(this.isElement(element) && element !== document.documentElement && element !== document.body && element !==window){
            scrollWidth=element.scrollWidth;
        }else{
            if(document.documentElement.scrollWidth === 0 || document.body.scrollWidth ===0){
                scrollWidth=document.documentElement.scrollWidth || document.body.scrollWidth;
            }else{
                scrollWidth=document.documentElement.scrollWidth > document.body.scrollWidth ? 
                    document.documentElement.scrollWidth : document.body.scrollWidth;
            };
        };
        return scrollWidth;
    },
    /**
     * 获取某个元素的总高度
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认表示整个页面文档
     * @returns 返回元素总高度值
     */
     getNodeHeight(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        };
        let scrollHeight=0;
        if(this.isElement(element) && element !== document.documentElement && element !== document.body && element !==window){
            scrollHeight=element.scrollHeight;
        }else{
            if(document.documentElement.scrollHeight === 0 || document.body.scrollHeight ===0){
                scrollHeight=document.documentElement.scrollHeight || document.body.scrollHeight;
            }else{
                scrollHeight=document.documentElement.scrollHeight > document.body.scrollHeight ? 
                    document.documentElement.scrollHeight : document.body.scrollHeight;
            };
        };
        return scrollHeight;
    },
    /**
     * 获取某个元素的滚动条在Y轴上滚动的距离
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是窗口滚动
     * @returns 返回元素垂直滚动距离
     */
    getNodeScrollTop(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        };
        let scrollTop=0;
        if(this.isElement(element) && element !== document.documentElement && element !== document.body && element !==window){
            scrollTop=element.scrollTop;
        }else{
            if(document.documentElement.scrollTop === 0 || document.body.scrollTop ===0){
                scrollTop=document.documentElement.scrollTop || document.body.scrollTop;
            }else{
                scrollTop=document.documentElement.scrollTop > document.body.scrollTop ? 
                    document.documentElement.scrollTop : document.body.scrollTop;
            };
        };
        return scrollTop;
    },
    /**
     * 获取某个元素的滚动条在X轴上滚动的距离
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是窗口滚动
     * @returns 返回元素水平滚动距离
     */
    getNodeScrollLeft(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        };
        let scrollLeft=0;
        if(this.isElement(element) && element !== document.documentElement && element !== document.body && element !==window){
            scrollLeft=element.scrollLeft;
        }else{
            if(document.documentElement.scrollLeft === 0 || document.body.scrollLeft ===0){
                scrollLeft=document.documentElement.scrollLeft || document.body.scrollLeft;
            }else{
                scrollLeft=document.documentElement.scrollLeft > document.body.scrollLeft ? 
                    document.documentElement.scrollLeft : document.body.scrollLeft;
            };
        };
        return scrollLeft;
    },
    /**
     * 监听某个元素滚动至顶部或底部的事件
     * @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是窗口滚动
     * @param {Function} callback 回调函数
     * @returns 无返回值
     */
    TriggerScrollTopOrBottom(element,callback){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        };
        let scrollElement=window;
        if(this.isElement(element) && element !== document.body && element !== document.documentElement){
            scrollElement=element;
        }else if(typeof element === "function"){
            callback=element;
        };
        // 滑动到底部时是否触发回调函数的标识，解决ios系统下多次触发回调的bug
        let flag=true;
        scrollElement.addEventListener("scroll",(event)=>{
            if(this.getNodeScrollTop(scrollElement) <= 0 ){ //滑动到顶部
                let options={
                    state:"top",
                    target:scrollElement
                };
                if(!flag){
                    return;
                }else if(typeof callback === "function"){
                    flag=false;
                    callback(options);
                };
            }else{ //滑动到底部
                let options={
                    state:"bottom",
                    target:scrollElement
                };
                let height=0;
                if(scrollElement === window){
                    height=window.innerHeight;
                }else{
                    height=scrollElement.clientHeight;
                };
                // +1防止出现小数点误差
                if(QilinNumber.addNumber(this.getNodeScrollTop(scrollElement),height) + 1 >= this.getNodeHeight(scrollElement) && height !== this.getNodeHeight){
                    if(!flag){
                        return;
                    }else if(typeof callback === "function"){
                        flag=false;
                        callback(options);
                    };
                }else{
                    flag=true;
                };
            };
        });
    },
    /**
     * 设置某个元素在Y轴上的滚动距离
     * @param {Element} element 可以是元素节点也可以是选择器--不传默认是窗口滚动
     * @param {Number} number 数字 表示滚动条滚动的距离 默认为0
     * @param {Number} time 数字 表示滚动持续时间 默认为0
     * @returns 返回Promise对象，通过then方法回调，表示设置滚动后的事件
     */
    setNodeScrollTop(element,number=0,time=0){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        }else if(typeof number !== "number"){
            throw new TypeError("第二个参数必须是数字类型");
        }else if(typeof time !== "number"){
            throw new TypeError("第三个参数必须是数字类型");
        };
        let isWindow=false;
        if(!this.isElement(element) || element === document.body || element === document.documentElement || element === window){
            isWindow=true;
        };
        return new Promise((resolve,reject)=>{
            if(time <= 0){ //若滚动持续时间小于等于0，则无动画效果，直接设置滚动距离
                if(isWindow){
                    document.documentElement.scrollTop=document.body.scrollTop=number;
                }else{
                    element.scrollTop=number;
                };
                resolve();
            }else{ //若滚动持续时间大于0，则设置滚动动画效果
                let scrollTime=10; //设置循环的间隔时间，值越小消耗性能越高
                let scrollCount=QilinNumber.divideNumber(time,scrollTime); //计算循环的次数
                let currentScrollTop=this.getNodeScrollTop(element); //获取当前滚动条垂直滚动距离
                let everyScrollTop=QilinNumber.divideNumber(QilinNumber.minusNumber(number,currentScrollTop),scrollCount); //计算每次垂直滚动的距离
                let ScrollTimer=setInterval(()=>{
                    if(scrollCount > 0 ){
                        scrollCount--;
                        if(isWindow){
                            document.documentElement.scrollTop=document.body.scrollTop=currentScrollTop=QilinNumber.addNumber(currentScrollTop,everyScrollTop);
                        }else{
                            element.scrollTop=currentScrollTop=QilinNumber.addNumber(currentScrollTop,everyScrollTop);
                        };
                    }else{
                        clearInterval(ScrollTimer);
                        resolve();
                    };
                },scrollTime);
            };
        });
    },
    /**
     * 设置某个元素在X轴上的滚动距离
     * @param {Element} element 可以是元素节点也可以是选择器--不传默认是窗口滚动
     * @param {Number} number 数字 表示滚动条滚动的距离 默认为0
     * @param {Number} time 数字 表示滚动持续时间 默认为0
     * @returns 返回Promise对象，通过then方法回调，表示设置滚动后的事件
     */
    setNodeScrollLeft(element,number=0,time=0){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        }else if(typeof number !== "number"){
            throw new TypeError("第二个参数必须是数字类型");
        }else if(typeof time !== "number"){
            throw new TypeError("第三个参数必须是数字类型");
        };
        let isWindow=false;
        if(!this.isElement(element) || element === document.body || element === document.documentElement || element === window){
            isWindow=true;
        };
        return new Promise((resolve,reject)=>{
            if(time <= 0){ //若滚动持续时间小于等于0，则无动画效果，直接设置滚动距离
                if(isWindow){
                    document.documentElement.scrollLeft=document.body.scrollLeft=number;
                }else{
                    element.scrollLeft=number;
                };
                resolve();
            }else{ //若滚动持续时间大于0，则设置滚动动画效果
                let scrollTime=10; //设置循环的间隔时间，值越小消耗性能越高
                let scrollCount=QilinNumber.divideNumber(time,scrollTime); //计算循环的次数
                let currentScrollLeft=this.getNodeScrollLeft(element); //获取当前滚动条水平滚动距离
                let everyScrollLeft=QilinNumber.divideNumber(QilinNumber.minusNumber(number,currentScrollLeft),scrollCount); //计算每次垂直滚动的距离
                let ScrollTimer=setInterval(()=>{
                    if(scrollCount > 0 ){
                        scrollCount--;
                        if(isWindow){
                            document.documentElement.scrollLeft=document.body.scrollLeft=currentScrollLeft=QilinNumber.addNumber(currentScrollLeft,everyScrollLeft);
                        }else{
                            element.scrollLeft=currentScrollLeft=QilinNumber.addNumber(currentScrollLeft,everyScrollLeft);
                        };
                    }else{
                        clearInterval(ScrollTimer);
                        resolve();
                    };
                },scrollTime);
            };
        });
    },
    /**
     * 获取某个元素距离可视窗口的位置
     *  @param {Element | String} element 可以是元素节点也可以是选择器--不传默认是body元素--若存在多个相同的选择器则默认为第一个
     * @returns 返回一个对象--包含距离可视窗口各个位置的距离
     */
    getNodeBounding(element){
        if(element && typeof element === "string"){
            element=document.body.querySelector(element);
        }else if(!this.isElement(element)){
            element=document.body;
        };
        let point=element.getBoundingClientRect();
        let top=point.top; //元素顶部距离可视窗口上边的距离
        let bottom=QilinNumber.minusNumber(document.documentElement.clientHeight || window.innerHeight,point.bottom); //元素底部距离可视窗口底部的距离
        let left=point.left; //元素左侧距离可视窗口左侧的距离
        let right=QilinNumber.minusNumber(document.documentElement.clientWidth || window.innerWidth,point.right); //元素右侧距离可视窗口右侧的距离
        return {
            top,
            bottom,
            left,
            right
        };
    }
};